#include "FileDataExtractor.h"

ULONG crc32_table[256];
ULONG ulPolynomial = 0x04c11db7;

FileHeader* FileDataExtractor::createFileHeader(const char* filepath)
{
    FileHeader* fheader = new FileHeader();
    fheader->version_ = 20;
    fheader->flags_ = 0;
    fheader->method_ = 0;
    fheader->name_ = getName(filepath);
    fheader->name_len = strlen(fheader->name_);

    tm* file_time = getLastModifiedTime(filepath);
    fheader->time_ = calculate_modified_time(file_time);
    makeLittleIndian(fheader->time_);
    fheader->date_ = calculate_modified_date(file_time);
    makeLittleIndian(fheader->date_);
    FILE* file = fopen(filepath, "rb");
    if (file)
    {
        long bufsize = fileSize(file);
        unsigned char *buffer = new unsigned char[bufsize];

        if (buffer)
        {
            long result = fread(buffer, 1, bufsize, file);
            fclose(file);
            if (result != bufsize)
                printf("\nError reading file %s\n", filepath);
            else
            {
                fheader->crc32_ = getCRC(buffer, bufsize);
                fheader->content_ = buffer;
            }
        }
        fheader->size_uncomp = fheader->size_comp = bufsize;
    }
    fheader->extrafield_len = 0;
    return fheader;
}

CentralDirectory* FileDataExtractor::create_cdirectory(FileHeader* header)
{
    CentralDirectory* cdirectory = new CentralDirectory(header);
    cdirectory->commentLenght_ = 0;
    cdirectory->comments_ = 0;
    cdirectory->diskStart_ = 0;
    cdirectory->offset_ = 0;
    cdirectory->versionMadeBy_ = 0x0317;
    cdirectory->intAttr_ = 0;
    cdirectory->extAttr_ = 0777 << 16L;

    return cdirectory;
}

char* FileDataExtractor::getName(const char* filepath)
{
    string filename = filepath;
    size_t lastSlashIndex = filename.find_last_of("\\/");
    if (string::npos != lastSlashIndex)
        filename.erase(0, lastSlashIndex + 1);
    char *name = new char[strlen(filename.c_str()) + 1];
    strcpy(name, filename.c_str());
    return name;
}

tm* FileDataExtractor::getLastModifiedTime(const char* filepath)
{
    struct tm* time;
    struct stat attribute;

    stat(filepath, &attribute);
    time = localtime(&attribute.st_mtime);

    return time;
}

unsigned short FileDataExtractor::calculate_modified_time(tm* time)
{
    short first_pos(11), second_pos(5);

    unsigned short codedValue = time->tm_hour << first_pos;
    codedValue |= (time->tm_min << second_pos);
    // seconds must be divided by 2 right as zip specification declares
    codedValue |= (time->tm_sec / 2);
    // time and date must be written as little indian
    return codedValue;
}

unsigned short FileDataExtractor::calculate_modified_date(tm* time)
{
    short first_pos(9), second_pos(5);

    unsigned short codedValue = time->tm_year << first_pos;
    codedValue |= time->tm_mon << second_pos;
    codedValue |= time->tm_mday;
    return codedValue;
}

void FileDataExtractor::makeLittleIndian(unsigned short& codedValue)
{
    short size = sizeof (codedValue);
    short half = size << 2;
    codedValue = codedValue << half | codedValue >> half;
}

void FileDataExtractor::initCrcTable()
{

    // 256 values representing ASCII character codes.
    for (int i = 0; i <= 0xFF; i++)
    {
        crc32_table[i] = reflect(i, 8) << 24;
        for (int j = 0; j < 8; j++)
            crc32_table[i] = (crc32_table[i] << 1) ^ (crc32_table[i] & (1 << 31) ? ulPolynomial : 0);
        crc32_table[i] = reflect(crc32_table[i], 32);
    }

}

ULONG FileDataExtractor::reflect(ULONG ref, char ch)
{ // Used only by Init_CRC32_Table()

    ULONG value(0);

    // Swap bit 0 for bit 7
    // bit 1 for bit 6, etc.
    for (int i = 1; i < (ch + 1); i++)
    {
        if (ref & 1)
            value |= 1 << (ch - i);
        ref >>= 1;
    }
    return value;
}

ULONG FileDataExtractor::getCRC(unsigned char* buffer, ULONG bufsize)
{
    initCrcTable();
    
    ULONG crc(0xffffffff);
    int len;
    len = bufsize;
    // Save the text in the buffer.

    // Perform the algorithm on each character
    // in the string, using the lookup table values.

    for (int i = 0; i < len; i++)
        crc = (crc >> 8) ^ crc32_table[(crc & 0xFF) ^ buffer[i]];

    // Exclusive OR the result with the beginning value.
    return crc^0xffffffff;

}

long FileDataExtractor::fileSize(FILE *input)
{

    long fileSizeBytes;
    fseek(input, 0, SEEK_END);
    fileSizeBytes = ftell(input);
    fseek(input, 0, SEEK_SET);

    return fileSizeBytes;
}